package rpc;

import com.hoppinzq.service.common.HoppinInvocationRequest;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import io.netty.channel.socket.SocketChannel;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class RpcClient {
    private final Map<String, Object> proxyCache = new ConcurrentHashMap<>();
    public <T> T createProxy(Class<T> interfaceClass, String host, int port) {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass},
                (proxy, method, args) -> {
                    if (Object.class.equals(method.getDeclaringClass())) {
                        try {
                            return method.invoke(this, args);
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    } else {
                        HoppinInvocationRequest request = new HoppinInvocationRequest();
                        request.setServiceName(interfaceClass.getName());
                        request.setMethodName(method.getName());
                        request.setParameterTypes(method.getParameterTypes());
                        request.setParameters(args);
                        RpcClientHandler handler = new RpcClientHandler();
                        EventLoopGroup group = new NioEventLoopGroup();
                        try {
                            Bootstrap bootstrap = new Bootstrap();
                            bootstrap.group(group)
                                    .channel(NioSocketChannel.class)
                                    .handler(new ChannelInitializer<SocketChannel>() {
                                        @Override
                                        protected void initChannel(SocketChannel ch) throws Exception {
                                            ChannelPipeline pipeline = ch.pipeline();
                                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
                                                    .addLast(new LengthFieldPrepender(4))
                                                    .addLast(new ObjectEncoder())
                                                    .addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)))
                                                    .addLast(handler);
                                        }
                                    })
                                    .option(ChannelOption.SO_KEEPALIVE, true);
                            ChannelFuture future = bootstrap.connect(host, port).sync();
                            future.channel().writeAndFlush(request).sync();
                            future.channel().closeFuture().sync();
                        } finally {
                            group.shutdownGracefully();
                        }
                        return handler.getResponse();
                    }
                    return null;
                });
    }
}